﻿using System;
using System.Collections.Generic;
using Argos.Data.Model;
using System.Text;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Runtime;
using System.Collections;
using Argos.Activities.MessageDelivery;
using System.Globalization;

namespace Argos.WorkflowServices
{
	/// <summary>
	/// An implementation of the <see cref="IAlertingService"/> interface that can be used as a
	/// workflow service in a process that is hosting Argos monitoring workflows.
	/// </summary>
	/// <remarks>
	/// Developers can extend this class to customize alert delivery.
	/// </remarks>
	public class AlertingService : WorkflowRuntimeService , IAlertingService
	{

		#region #ctor: public AlertingService()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public AlertingService() { }

		#endregion
		

		
		#region private IStorageService StorageService { get; }

		private IStorageService _StorageService;

		private IStorageService StorageService
		{
			get
			{
				if (null == _StorageService)
				{
					_StorageService = this.Runtime.GetService<IStorageService>();
					if (null == _StorageService)
						throw new InvalidOperationException(string.Format("The current workflow runtime does not contain a service that implements interface '{0}' which is required by the current operation.", typeof(IStorageService)));
				}
				return _StorageService;
			}
		}

		#endregion



		#region public void DeliverAlert(Guid targetId, AlertTypes alertType, params object[] data)

		/// <summary>
		/// Delivers the alert to all recipients for the given target.
		/// </summary>
		/// <param name="targetId">The id of the target whose alert to be delivered.</param>
		/// <param name="alertType">The type of alert to deliver.</param>
		/// <param name="data">Data that builds up the body of the alert message.</param>
		public void DeliverAlert(Guid targetId, AlertTypes alertType, params object[] data)
		{

			List<Recipient> recipients = this.StorageService.GetRecipients(targetId, alertType);
			if (null == recipients)
			{
				return;
			}

			Target target = this.StorageService.GetTarget(targetId);
			
			foreach (Recipient recipient in recipients)
			{
				string subject = this.FormatAlertSubject(recipient.DeliveryRoute.DeliveryActivityType, target, alertType, data);
				string body = this.FormatAlertMessageBody(recipient.DeliveryRoute.DeliveryActivityType, target, alertType, data);

				AlertMessage msg = new AlertMessage();
				msg.AlertType = alertType;
				msg.Body = body;
				msg.BodyContentType = this.GetAlertMessageBodyContentType(recipient.DeliveryRoute.DeliveryActivityType, body);
				msg.RecipientAddress = recipient.Address;
				msg.RecipientName = recipient.Name;
				msg.Subject = subject;
				msg.Target = target;

				if (null != data)
				{
					foreach (object item in data)
					{
						if (null != item)
							msg.RawData.Add(item);
					}
				}

				recipient.DeliveryRoute.DeliveryActivityProperties["Message"] = msg;

				WorkflowInstance instance = this.Runtime.CreateWorkflow(recipient.DeliveryRoute.DeliveryActivityType, recipient.DeliveryRoute.DeliveryActivityProperties);
				instance.Start();
			}
		}

		#endregion

		#region protected virtual string FormatAlertMessageBody(Type messageDeliveryActivityType, Target target, AlertTypes alertType, params object[] data)

		/// <summary>
		/// Formats the alert message body suitable for the given message delivery activity.
		/// </summary>
		/// <param name="messageDeliveryActivityType">The type of the message delivery activity to format the message body for.</param>
		/// <param name="target">The target for which to format the message body.</param>
		/// <param name="alertType">The type of alert.</param>
		/// <param name="data">An array of items to be included in the formatted body.</param>
		protected virtual string FormatAlertMessageBody(Type messageDeliveryActivityType, Target target, AlertTypes alertType, params object[] data)
		{
			StringBuilder sb = new StringBuilder();

			sb.AppendFormat("{0} : {1} ({2})", alertType, null != target ? target.Name : "<Unknown Target>", this.FormatDateTimeForAlert(DateTimeOffset.Now));

			if (null != target && !string.IsNullOrEmpty(target.Description))
			{
				sb.AppendLine();
				sb.AppendLine();
				sb.AppendLine("Target Description:");
				sb.AppendLine(target.Description);
			}

			if (null != data)
			{

				foreach (object item in data)
				{
					if (null != item)
					{
						string name = null, value = null;

						if (item is KeyValuePair<string, object>)
						{
							name = ((KeyValuePair<string, object>)item).Key;
							value = this.FormatItem(((KeyValuePair<string, object>)item).Value);
						}
						else if (item is Exception)
						{
							value = this.FormatItem(item);
						}
						else
						{
							name = item.GetType().FullName;
							value = this.FormatItem(item);
						}

						if (!string.IsNullOrEmpty(value))
						{
							sb.AppendLine();
							sb.AppendLine();
							if (!string.IsNullOrEmpty(name))
							{
								sb.AppendFormat("{0} : ", name);
							}
							sb.Append(value);
						}
					}
				}
			}

			return sb.ToString();
		}

		#endregion

		#region protected virtual string FormatAlertSubject(Type messageDeliveryActivityType, Target target, AlertTypes alertType, params object[] data)

		/// <summary>
		/// Formats the subject for the alert message.
		/// </summary>
		/// <param name="messageDeliveryActivityType">The type of message delivery activity to format the subject for.</param>
		/// <param name="target">The target for which to format the subject.</param>
		/// <param name="alertType">The type of alert.</param>
		/// <param name="data">An array of items containing data for the message.</param>
		protected virtual string FormatAlertSubject(Type messageDeliveryActivityType, Target target, AlertTypes alertType, params object[] data)
		{
			if (null == target)
			{
				return string.Format("{0} ({1})", alertType, this.FormatDateTimeForAlert(DateTimeOffset.Now));
			}
			return string.Format("{0} : {1} ({2})", alertType, target.Name, this.FormatDateTimeForAlert(DateTimeOffset.Now));
		}

		#endregion

		#region protected virtual string FormatDateTimeForAlert(DateTimeOffset dateTime)

		/// <summary>
		/// When implemented in a derived class, formats the given <see cref="DateTimeOffset"/> instance to
		/// a string that is used in both the subject and body of the alert message.
		/// </summary>
		/// <param name="dateTime">The date and time to format.</param>
		protected virtual string FormatDateTimeForAlert(DateTimeOffset dateTime)
		{
			// Use a swedish culture, which produces a date that is formatted as
			// YYYY-MM-DD, but the time zone is still preserved and written out
			// to the produced string. If we would use the format specifier "u",
			// the date would be formatted in the same way, but the time would
			// be converted to UTC and a 'Z' would be appended to the string.
			return dateTime.ToString(null, new CultureInfo("sv-se"));
		}

		#endregion
		
		#region protected virtual string GetAlertMessageBodyContentType(Type messageDeliveryActivityType, string messageBody)

		/// <summary>
		/// When implemented in a class, returns the content type, for instance <c>text/plain</c>, for
		/// the message body created in the <see cref="FormatAlertMessageBody()"/> method.
		/// </summary>
		/// <param name="messageDeliveryActivityType">The message delivery activity type that will deliver the message.</param>
		/// <param name="messageBody">The message body. This parameter is mainly for reference.</param>
		/// <returns>
		/// Returns the content type as a lower case string. Normally, this is either <c>text/plain</c>
		/// or <c>text/html</c>.
		/// </returns>
		protected virtual string GetAlertMessageBodyContentType(Type messageDeliveryActivityType, string messageBody)
		{
			return System.Net.Mime.MediaTypeNames.Text.Plain;
		}

		#endregion



		#region private string FormatItem(object item)

		private string FormatItem(object item)
		{
			if (null == item)
			{
				return null;
			}

			if (item is ICollection)
			{
				StringBuilder sb = new StringBuilder();
				ArrayList arrList = new ArrayList((ICollection)item);
				for (int i = 0; i < arrList.Count; i++)
				{
					if (null != arrList[i])
					{
						if (i > 0)
						{
							sb.Append(", ");
						}
						sb.Append(arrList[i]);
					}
				}
				return sb.ToString();
			}

			return item.ToString();
		}

		#endregion

	}
}
